package qq2564874169.tuaotuao.fx.context;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.support.GenericApplicationContext;
import qq2564874169.tuaotuao.fx.FxStream;
import qq2564874169.tuaotuao.fx.Task;
import qq2564874169.tuaotuao.fx.utils.ClassUtil;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

public class SpringContextExtend {
    private static Map<String, Class<?>> beanClasses = new HashMap<>(8);
    private SpringContextExtend parent;
    private GenericApplicationContext context;
    private ConfigurableListableBeanFactory factory;
    private ContextScope contextScope = new ContextScope();
    private Map<String, SpringContextExtend> childes = new HashMap<>(8);
    private List<DefaultScopeSetting> defScopes = new ArrayList<>(8);
    private Map<String, Object> externalBean = new HashMap<>(8);
    private List<Object> protoDisposables = new ArrayList<>();

    public SpringContextExtend(GenericApplicationContext applicationContext) {
        context = applicationContext;
        factory = context.getBeanFactory();
        factory.registerScope(BeanContext.SCOPE_CONTEXT, contextScope);
        savePrototypeBean();

        context.addBeanFactoryPostProcessor(this::contextIsReady);
        context.addApplicationListener((ApplicationListener<ContextClosedEvent>) e -> onClosed(e));
    }

    private void contextIsReady(ConfigurableListableBeanFactory factory) {
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(CommonAnnotationBeanPostProcessor.class, () -> new FxCommonAnnotationBeanPostProcessor());
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class, () -> new FxAutowiredAnnotationBeanPostProcessor());

        //把父容器中scope_context的bean注册到当前容器
        //重新注册scope_prototype才能触发创建事件，用于收集原型bean，否则只会触发父级的事件
        if (parent != null) {
            for (var name : parent.context.getBeanDefinitionNames()) {
                BeanDefinition def = parent.context.getBeanDefinition(name);
                if (BeanContext.SCOPE_CONTEXT.equals(def.getScope()) ||
                        BeanContext.SCOPE_PROTOTYPE.equals(def.getScope())) {
                    context.registerBeanDefinition(name, def);
                }
            }
        }
        setDefaultScope();
    }

    /**
     * 收集原型作用域的bean，用于在合适的时候释放它们
     */
    private void savePrototypeBean() {
        factory.addBeanPostProcessor(new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (!context.isRunning()) {
                    return bean;
                }
                var func = protoDestroyFunc(bean, beanName);
                if (func != null) {
                    protoDisposables.add(func);
                }
                return bean;
            }

            private Object protoDestroyFunc(Object bean, String beanName) {
                if (bean == context) {
                    return null;
                }
                if (!context.isBeanNameInUse(beanName)) {
                    return null;
                }
                if (!factory.isPrototype(beanName)) {
                    return null;
                }
                if (externalBean.containsKey(beanName)) {
                    return null;
                }
                BeanDefinition def = context.getBeanDefinition(beanName);
                if (def.getDestroyMethodName() != null || bean instanceof DisposableBean || bean instanceof AutoCloseable) {
                    return bean;
                }
                return null;
            }
        });
    }

    public void setBean(Class<?> beanClass, Object bean) {
        context.registerBean(beanClass, (Supplier) () -> bean, bd -> {
            bd.setScope(BeanContext.SCOPE_SINGLETON);
            bd.setPrimary(true);
        });
        var names = context.getBeanNamesForType(beanClass);
        for (var name : names) {
            externalBean.put(name, bean);
        }
    }

    public void addChild(SpringContextExtend child) {
        child.parent = this;
        childes.put(child.context.getId(), child);
    }

    public void addDefaultScope(DefaultScopeSetting setting) {
        defScopes.add(setting);
    }

    private boolean isConfigClass(BeanDefinition def) {
        int count = 0;
        for (String item : def.attributeNames()) {
            if (item.endsWith("configurationClass") || item.endsWith("preserveTargetClass")) {
                if (++count >= 2) {
                    return true;
                }
            }
        }
        return false;
    }

    private DefaultScopeSetting defscopeMatches(String className) {
        List<DefaultScopeSetting> list = new ArrayList<>();
        for (DefaultScopeSetting item : defScopes) {
            if (item.matches(className)) {
                list.add(item);
            }
        }
        if (list.isEmpty()) {
            return null;
        } else if (list.size() == 1) {
            return list.get(0);
        } else {
            return FxStream.create(list).max(i -> i.target.length());
        }
    }

    public void onNewBeanDef(String beanName, BeanDefinition def) {
        setDefaultScope(beanName, def);
    }

    public void onRemoveBeanDef(String beanName) {

    }

    private void setDefaultScope(String beanName, BeanDefinition def) {
        var beanClass = beanClasses.getOrDefault(beanName, null);
        if (beanClass == null) {
            if (def.getRole() >= 2) {
                return;
            }
            if (def.getBeanClassName() != null) {
                if (isConfigClass(def)) {
                    return;
                }
                beanClass = ClassUtil.forName(def.getBeanClassName());
                if (beanClass == null) {
                    return;
                }
                if (ClassUtil.haveAnno(beanClass, Scope.class)) {
                    return;
                }
            } else if (def.getFactoryBeanName() != null && def.getFactoryMethodName() != null && def.getFactoryMethodName() != null) {
                if ("".equals(def.getScope()) == false) {
                    return;
                }
                if (context.containsBean(def.getFactoryBeanName()) == false) {
                    return;
                }
                BeanDefinition mDef = context.getBeanDefinition(def.getFactoryBeanName());
                Class<?> clazz = ClassUtil.forName(mDef.getBeanClassName());
                Method method = ClassUtil.getMethod(clazz, def.getFactoryMethodName());
                //noinspection DataFlowIssue
                beanClass = method.getReturnType();
            } else {
                return;
            }
            if (beanClass == Void.class) {
                return;
            }
            beanClasses.put(beanName, beanClass);
        }
        DefaultScopeSetting setting = defscopeMatches(beanClass.getCanonicalName());
        if (setting != null && setting.scope.equals(def.getScope()) == false) {
            def.setScope(setting.scope);
        }
    }

    private void setDefaultScope() {
        if (defScopes.isEmpty()) {
            return;
        }
        for (String name : context.getBeanDefinitionNames()) {
            BeanDefinition def = context.getBeanDefinition(name);
            setDefaultScope(name, def);
        }
    }

    public <T> List<Class<T>> getClassListForType(Class<T> clazz) {
        var list = new ArrayList<Class<T>>();
        var names = context.getBeanNamesForType(clazz);
        for (String name : names) {
            if (beanClasses.containsKey(name)) {
                list.add((Class<T>) beanClasses.get(name));
            }
        }
        return list;
    }

    private void onClosed(ContextClosedEvent e) {
        if (e.getApplicationContext() != context) {
            return;
        }
        //关闭子容器
        if (childes.isEmpty() == false) {
            Task.foreach(childes.values(), i -> i.context.close());
            childes.clear();
        }
        //释放原型bean
        if (protoDisposables.isEmpty() == false) {
            Task.foreach(protoDisposables, i -> {
                if (i instanceof java.lang.AutoCloseable ac) {
                    try {
                        ac.close();
                    } catch (Exception ex) {
                        //...
                    }
                } else {
                    factory.destroyBean(i);
                }
            });
            protoDisposables.clear();
        }
        //排除外部bean的释放
        var fac = (DefaultSingletonBeanRegistry) factory;
        for (var name : externalBean.keySet()) {
            //noinspection DataFlowIssue
            fac.registerDisposableBean(name, null);
        }
        externalBean.clear();
        contextScope.destroy();
        defScopes.clear();
        beanClasses.clear();
        if (parent != null) {
            parent.childes.remove(context.getId());
        }
    }

    private class FxCommonAnnotationBeanPostProcessor extends CommonAnnotationBeanPostProcessor {
        @Override
        public boolean requiresDestruction(Object bean) {
            //外部bean不执行某些操作
            if (SpringContextExtend.this.externalBean.containsValue(bean) == false) {
                return super.requiresDestruction(bean);
            }
            return false;
        }

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            //外部bean不执行某些操作
            if (SpringContextExtend.this.externalBean.containsKey(beanName) == false) {
                return super.postProcessBeforeInitialization(bean, beanName);
            }
            return bean;
        }
    }

    private class FxAutowiredAnnotationBeanPostProcessor extends AutowiredAnnotationBeanPostProcessor {
        @Override
        public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
            //外部bean不执行某些操作
            if (SpringContextExtend.this.externalBean.containsKey(beanName) == false) {
                return super.postProcessProperties(pvs, bean, beanName);
            }
            return pvs;
        }
    }
}
